package whf.framework.web.struts;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.MessageResources;

import whf.framework.config.ApplicationConfig;
import whf.framework.entity.AbstractEntity;
import whf.framework.entity.Entity;
import whf.framework.exception.AppException;
import whf.framework.exception.AuthorizationException;
import whf.framework.exception.handlers.ExceptionChainHandle;
import whf.framework.exception.handlers.ExceptionHandleEndException;
import whf.framework.i18n.ApplicationResource;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.log.util.LogUtil;
import whf.framework.meta.Meta;
import whf.framework.meta.MetaManager;
import whf.framework.meta.MetaNotFoundException;
import whf.framework.meta.entity.IMeta;
import whf.framework.meta.entity.IProperty;
import whf.framework.meta.entity.IReadonlyProperty;
import whf.framework.meta.service.ObjectServiceImp;
import whf.framework.meta.service.ViewServiceImp;
import whf.framework.security.UserContext;
import whf.framework.service.Service;
import whf.framework.util.BeanFactory;
import whf.framework.util.BeanInitialization;
import whf.framework.util.BeanUtils;
import whf.framework.util.ParamUtils;
import whf.framework.util.StringUtils;
import whf.framework.util.ThreadContext;
import whf.framework.util.Utils;
import whf.framework.validate.ValidateErrors;
import whf.framework.validate.ValidateException;
import whf.framework.validate.ValidateService;
import whf.framework.validate.ValidateServiceImp;
import whf.framework.web.Constants;

/**
 * @author wanghaifeng
 * 
 */
@SuppressWarnings("unchecked")
public abstract class DispatchAction extends Action {

	private static Log log = LogFactory.getLog(DispatchAction.class);

	protected static MessageResources messages = MessageResources.getMessageResources("org.apache.struts.actions.LocalStrings");

	protected final Map<String, String> defaultQueryParameters = getDefaultQueryParameters();

	protected final ValidateService validateService = new ValidateServiceImp();

	protected Class clazz = this.getClass();

	protected HashMap<String, Method> methods = new HashMap<String, Method>();

	private Meta meta;

	static { // 注册类型转换器
		Map<Converter, Class> converters = ApplicationConfig.getInstance().getStrutsConverters();
		for (Converter c : converters.keySet()) {
			Class cls = converters.get(c);
			ConvertUtils.register(c, cls);
			log.debug("Converter:" + cls + "\t -> \t" + c);
		}
	}

	/**
	 * 获取当前元数据
	 * 
	 * @modify wanghaifeng Aug 24, 2006 11:15:40 PM
	 * @return
	 * @throws MetaNotFoundException
	 */
	protected Meta getMeta() {
		return MetaManager.getDefaultInstance().findByActionClass(this.getClass().getName()).toMeta();
	}

	/**
	 * 获取当前类的服务
	 * 
	 * @modify wanghaifeng Aug 24, 2006 11:15:10 PM
	 * @return
	 */
	protected Service getService() {
		try {
			Service service = BeanFactory.getService(getMeta());
			return service;
		} catch (Exception e) {
			log.error(this, e);
			return null;
		}
	}

	/**
	 * 获取对象属性能描述信息
	 * 
	 * @modify wanghaifeng Aug 24, 2006 11:14:28 PM
	 * @return
	 */
	protected final Map<String, PropertyDescriptor> getObjectPropertyDescriptors() {
		return BeanUtils.getObjectPropertyDescriptors(this.getMeta().getModelClass());
	}

	/**
	 * 获取缺省的查询参数
	 * 
	 * @modify wanghaifeng Aug 24, 2006 11:14:45 PM
	 * @return
	 */
	protected final Map<String, String> getDefaultQueryParameters() {
		Map<String, String> parameters = new HashMap<String, String>();
		Map<String, PropertyDescriptor> map = this.getObjectPropertyDescriptors();
		for (String propertyName : map.keySet()) {
			parameters.put(propertyName, "");
		}
		return parameters;
	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * 可能的Struts方法类型
	 * 
	 * @property Class[][]:POSSIBLE_METHOD_TYPES
	 */
	public static final Class[] METHOD_PARAMETER_TYPES = { AbstractEntity.class, WebData.class };

	// --------------------------------------------------------- Public Methods

	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			if (isCancelled(request)) {
				ActionForward af = cancelled(mapping, form, request, response);
				if (af != null) {
					return af;
				}
			}
			// Identify the request parameter containing the method name
			String parameter = mapping.getParameter();
			if (parameter == null) {
				String message = messages.getMessage("dispatch.handler", mapping.getPath());
				throw new ServletException(message);
			}

			String webMethodName = getMethodName(mapping, form, request, response, parameter);
			if ("execute".equals(webMethodName) || "perform".equals(webMethodName)) {
				String message = messages.getMessage("dispatch.recursive", mapping.getPath());
				throw new ServletException(message);
			}
			AbstractEntity webEntity = (AbstractEntity) form;
			webEntity.initializedByWeb();
			WebData webData = new WebData(request, response, mapping, webMethodName);
			// 保存Struts状态到jsp中使用
			request.setAttribute(Constants.STRUTS_FORMNAME, mapping.getName());
			request.setAttribute(Constants.STRUTS_PATH, mapping.getPath());
			request.setAttribute(Constants.STRUTS_FORWARD, mapping.getForward());
			request.setAttribute(Constants.STRUTS_PREFIX, mapping.getPrefix());
			request.setAttribute(Constants.STRUTS_SUFFIX, mapping.getSuffix());
			request.setAttribute(Constants.STRUTS_META, this.getMeta());
			// 预留保存状态用的域
			request.setAttribute(Constants.REQUEST_KEEP_PARAMETERS, ParamUtils.getKeepParameters(request));
			
			// 当前的struts路径,在TableTag中导航可以使用到！
			String navPath = request.getContextPath() + mapping.getPath();
			if (StringUtils.isEmpty(mapping.getSuffix())) {
				navPath = navPath + ".do";
			} else {
				navPath = navPath + "." + mapping.getSuffix();
			}
			// 日志记录
			long handleBegin = System.currentTimeMillis();
			// 调用方法------------------------------------------------------------------------------
			ActionForward result = null;
			try {
				ThreadContext.setCurrentMeta(this.getMeta());
				WebForward forward = dispatchMethod(webEntity, webData);
				if(forward != null)
					result = forward.getActionForward();
			} catch (ClassCastException e) {
				String message = messages.getMessage("dispatch.return", mapping.getPath(), webMethodName);
				log.error(message, e);
				throw e;
			} catch (IllegalAccessException e) {
				String message = messages.getMessage("dispatch.error", mapping.getPath(), webMethodName);
				log.error(message, e);
				throw e;
			} catch (InvocationTargetException e) {
				Throwable t = e.getTargetException();
				if (t instanceof Exception) {
					throw ((Exception) t);
				} else {
					String message = messages.getMessage("dispatch.error", mapping.getPath(), webMethodName);
					log.error(message, e);
					throw new ServletException(e);
				}
			}
			String lastestWebMethodName = (String) request.getAttribute(Constants.STRUTS_LASTEST_WEBMETHOD); // 避免Struts内部跳转后无法找到实际的路径
			if (lastestWebMethodName == null || "".equals(lastestWebMethodName)) {
				lastestWebMethodName = request.getParameter(parameter);
			}
			if (!StringUtils.isEmpty(lastestWebMethodName)) {
				navPath += "?" + mapping.getParameter() + "=" + lastestWebMethodName;
			}
			request.setAttribute(Constants.STRUTS_NAVIGATION_PATH, navPath);
			LogUtil.log(request, meta == null ? null : meta.getName(), System.currentTimeMillis() - handleBegin);
			return result;
		} catch (Exception e) {
			log.error(this, e);
			LogUtil.logAccessError(request, "error", meta == null ? null : meta.getName(), e, 0);

			Map<String, Object> parameters = Utils.newHashMap();
			parameters.put("mapping", mapping);
			parameters.put("form", form);
			parameters.put("request", request);
			parameters.put("response", response);
			Object result = null;
			try {
				ExceptionChainHandle.getExceptionChainHandle().handle(e, parameters);
			} catch (NullPointerException ne) {
				log.error(this, ne);
			} catch (ExceptionHandleEndException handleException) {
				result = handleException.getResult();
			}
			request.setAttribute("exception", e);
			if (result != null && result instanceof ActionForward) { // 返回结果
				return (ActionForward) result;
			} else if (result != null && result instanceof WebForward) {
				return ((WebForward) result).getActionForward();
			} else {
				throw e;
			}
		}
	}

	protected WebForward unspecified(AbstractEntity form, WebData data) throws Exception {
		return data.forward("error");
	}

	/**
	 * Method which is dispatched to when the request is a cancel button submit.
	 * Subclasses of <code>DispatchAction</code> should override this method
	 * if they wish to provide default behavior different than returning null.
	 * 
	 * @since Struts 1.2.0
	 */
	protected ActionForward cancelled(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

		return null;
	}

	// ----------------------------------------------------- Protected Methods

	/**
	 * Dispatch to the specified method.
	 * 
	 * @since Struts 1.1
	 */
	protected WebForward dispatchMethod(AbstractEntity webEntity, WebData data) throws Exception {
		String webMethodName = data.getWebMethod();
		if (webMethodName == null) {
			return this.unspecified(webEntity, data);
		} else if(webMethodName.indexOf("prepare") < 0 && (
					webMethodName.indexOf("update") >= 0 || 
					webMethodName.indexOf("create") >= 0 || 
					webMethodName.indexOf("delete") >= 0 || 
					webMethodName.indexOf("duplicate") >= 0)
				) {
			if(!StringUtils.equalsIgnoreCase(data.getRequest().getMethod(), "POST")) {
				throw new Exception ("对于数据修改操作必须是POST方法提交表单!");
			}
		}
		// Identify the method object to be dispatched to
		Method method = getMethod(webMethodName);
		if (method == null) {
			return data.forward("notFound");
		}
		/**
		 * end
		 */
		// 权限认证
		UserContext uc = data.getUserContext();
		String targetType = data.getParameter(Constants.FUNC_TARGET_TYPE);
		Meta meta = this.getMeta();
		if (targetType == null) {
			targetType = meta.getModelClass().getName();
		}
		//
		if (!uc.hasFunctionPermission(targetType, webMethodName)) {
			throw new AuthorizationException(ApplicationResource.get("exception.authorization.no_authority", new String[] { meta.getName(), webMethodName }));
		}
		// 方法调用
		Object args[] = new Object[] { webEntity, data };
		WebForward forward = (WebForward) method.invoke(this, args);
		return (forward);
	}

	protected Method getMethod(String name) {
		synchronized (methods) {
			Method method = (Method) methods.get(name);
			if (method == null) {
				try {
					method = clazz.getMethod(name, METHOD_PARAMETER_TYPES);
				} catch (Exception e) {
					// ignore
				}
			}
			return (method);
		}
	}

	/*
	 * end
	 */

	protected String getMethodName(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String parameter)
			throws Exception {
		return request.getParameter(parameter);
	}

	/**
	 * 合法性检查
	 * 
	 * @param request
	 * @param bo
	 * @param objectId
	 *            检查对象唯一id，用在一个page中多个tabletag的情况，有助于识别是哪个对象的属性发生错误，一般使用bo.class.getName()
	 * @return
	 */
	protected ValidateErrors validate(HttpServletRequest request, Entity bo, Meta meta) {
		ValidateErrors errors = null;
		try {
			Class validatingClass = meta.getModelClass();
			String metaType = request.getParameter(validatingClass.getName() + Constants.META_TYPE);
			long metaId = ParamUtils.getLongParameter(request, validatingClass.getName() + Constants.META, 0);
			IMeta validationMeta = null;
			Set<IProperty> properties = Utils.newHashSet();
			if (!StringUtils.equalsIgnoreCase(metaType, "Object") || metaId != meta.getId()) {
				if (StringUtils.equals(metaType, "Object")) {
					validationMeta = (IMeta) ObjectServiceImp.getObjectService().findByPrimaryKeyWithoutLazy(new Long(metaId));
				} else {
					validationMeta = (IMeta) ViewServiceImp.getViewService().findByPrimaryKeyWithoutLazy(new Long(metaId));
				}
				properties = validationMeta.getProperties();
			} else {
				properties = meta.getProperties();
			}
			Set<IReadonlyProperty> props = Utils.newHashSet();
			for (IProperty p : properties) {
				props.add((IReadonlyProperty) p);
			}
			this.validateService.validate(bo, props, validatingClass.getName());
		} catch (ValidateException e) {
			errors = e.getErrors();
		} catch (AppException appe) {
			log.error(this, appe);
		}
		return errors;
	}

	/**
	 * @modify wanghaifeng Apr 7, 2007 3:49:25 PM
	 * @param data
	 * @param bo
	 *            需要做合法性检查的对象
	 * @param meta
	 *            元数据对象
	 * @param validatingProperties
	 *            需要检查的属性，如果是null，使用元数据中的所有可用属性
	 * @return 如果有错误，返回错误列表
	 */
	protected ValidateErrors validate(final WebData data, final Entity bo, Meta meta, final Set<IReadonlyProperty> validatingProperties) {
		ValidateErrors errors = null;
		try {
			Class validatingClass = meta.getModelClass();
			Set<IReadonlyProperty> props = Utils.newHashSet();
			if (validatingProperties == null) {
				String metaType = data.getParameter(validatingClass.getName() + Constants.META_TYPE);
				long metaId = data.getLongParameter(validatingClass.getName() + Constants.META, 0);
				IMeta validationMeta = null;
				Set<IProperty> properties = Utils.newHashSet();
				if (!StringUtils.equalsIgnoreCase(metaType, "Object") || metaId != meta.getId()) {
					BeanInitialization.setInitializeProperties(new String[] { "properties" });
					if (StringUtils.equals(metaType, "Object")) {
						validationMeta = (IMeta) ObjectServiceImp.getObjectService().findByPrimaryKeyWithoutLazy(metaId);
					} else {
						validationMeta = (IMeta) ViewServiceImp.getViewService().findByPrimaryKeyWithoutLazy(metaId);
					}
					properties = validationMeta.getProperties();
				} else {
					properties = meta.getProperties();
				}
				for (IProperty p : properties) {
					props.add((IReadonlyProperty) p);
				}
			} else {
				props = validatingProperties;
			}
			this.validateService.validate(bo, props, validatingClass.getName());
		} catch (ValidateException e) {
			errors = e.getErrors();
		} catch (AppException appe) {
			log.error(this, appe);
		}
		return errors;
	}
}
